/**
 * ScriptParser.cs
 * 
 * AUTHOR:
 * Zlatko Michailov (http://blogs.msdn.com/esql)
 * 
 * PURPOSE:
 * Parses a script of a given grammar, executable keywords, and declaration keywords.
 * 
 * COPYRIGHT:
 * (c) Microsoft Corp., 2006-2007
 */



using System;
using System.Text;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using System.Diagnostics;



namespace Microsoft.Samples.Data.eSqlBlast
{
    public sealed class ScriptParser
    {
        // Data members
        private string _Input;
        private Grammar _ScriptGrammar;
        private Regex _ScriptRegex;
        private Regex _DeclarationKeywordsRegex;
        private MatchCollection _AllMatches;
        private bool _IsParsed;
        private ILanguage _Language;
        private Regex _LinePattern = new Regex(@"\n");


        public ScriptParser(ILanguage language)
        {
            _Language = language;
  
            _ScriptGrammar = new Grammar(_Language.GrammarDefinitions);
            _ScriptRegex = new Regex(_ScriptGrammar.Pattern, _Language.RegexOptions);
            if (_Language.DeclarationKeywords != null)
            {
                _DeclarationKeywordsRegex = new Regex(_Language.DeclarationKeywords, _Language.RegexOptions);
            }
            else
            {
                _DeclarationKeywordsRegex = null;
            }
            _IsParsed = false;
        }


        public void Parse(string input)
        {
            _Input = input;
            _AllMatches = _ScriptRegex.Matches(_Input);
            _IsParsed = true;
        }


        public MatchCollection AllMatches
        {
            get
            {
                return _AllMatches;
            }
        }


        public string Pattern
        {
            get
            {
                return _ScriptGrammar.Pattern;
            }
        }


        public IEnumerable<Statement> ExecutableStatements
        {
            get
            {
                if (!_IsParsed)
                {
                    throw new InvalidOperationException("Parse() must be called before any attempt to obtain the collection of executable statements.");
                }

                Statement executableStatement = new Statement(true);
                foreach (Match match in _AllMatches)
                {
                    if (match.Value.Length > 0)
                    {
                        string statementHead = match.Groups["StatementHead"].Value;
                        if (statementHead.Length == 0)
                        {
                            statementHead = match.Groups["Literal"].Value;
                        }

                        if (statementHead.Length > 0)
                        {
                            string statementText = match.Groups["Statement"].Value + _Language.StatementTerminator;
                            VisualLength offset = ComputeVisualLength(_Input.Substring(0, match.Index));
                            VisualLength size = ComputeVisualLength(statementText);

                            if (_DeclarationKeywordsRegex != null && _DeclarationKeywordsRegex.IsMatch(statementHead))
                            {
                                Statement statement = new Statement(statementText);
                                statement.Offset = offset;
                                statement.Size = size;
                                executableStatement.Declarations.Add(statement);
                            }
                            else
                            {
                                executableStatement.Text = statementText;
                                executableStatement.Offset = offset;
                                executableStatement.Size = size;
                                yield return executableStatement.Clone();
                            }
                        }
                    }
                }
                
            }
        }


        private VisualLength ComputeVisualLength(string input)
        {
            VisualLength visualLength;

            visualLength.Lines = visualLength.Columns = 0;
            visualLength.Lines = _LinePattern.Matches(input).Count + 1;
            if (visualLength.Lines == 1)
            {
                visualLength.Columns = input.Length;
            }
            else
            {
                visualLength.Columns = input.Length - _LinePattern.Matches(input)[visualLength.Lines - 2].Index - 1;
            }

            return visualLength;
        }

    }



    public sealed class Statement
    {
        public string Text;
        public VisualLength Offset;
        public VisualLength Size;
        public bool IsExecutable = false;
        public List<Statement> Declarations = null;


        public Statement()
        {
        }


        public Statement(bool isExecutable)
        {
            IsExecutable = isExecutable;
            if (isExecutable)
            {
                Declarations = new List<Statement>();
            }
        }


        public Statement(string text)
        {
            Text = text;
        }


        public Statement Clone()
        {
            Statement clone = new Statement();

            clone.Text = Text;
            clone.Offset = Offset;
            clone.Size = Size;
            clone.IsExecutable = IsExecutable;
            clone.Declarations = null;
            if (Declarations != null)
            {
                clone.Declarations = new List<Statement>(Declarations.GetRange(0, Declarations.Count));
            }

            return clone;
        }


        public override string  ToString()
        {
            StringBuilder builder = new StringBuilder();

            foreach (Statement declaration in Declarations)
            {
                builder.AppendFormat("{0}\n", declaration.Text);
            }
            builder.AppendFormat("\n{0}", Text);

            return builder.ToString();
        }

    }



    public struct VisualLength
    {
        public int Lines;
        public int Columns;
    }

}
